#include "Hotel.h"
#include "Reservation.h"
#include "RoomSingle.h"
#include "RoomDouble.h"
#include "RoomSuite.h"
#include "Price.h"
#include "Bill.h"

Hotel::Hotel( string file ) {
	// Open the passed file
	fileName = file;
	ifstream inn((fileName + ".dta").c_str());

	if (!inn.is_open()) {
		string error = "Hotel::Hotel, Failed to open hotel file: " + file;
		Error(error);
		return;
	}

	getline( inn, name );
	getline( inn, address );
	getline( inn, postalCode );	
	getline( inn, mailAddress );
	inn>> phone;					inn.ignore();
	inn>> fax;						inn.ignore();
	inn>> breakfastPrice;			inn.ignore();
	inn>> extraBedPrice;			inn.ignore();
	inn>> numFacilities;			inn.ignore();
	
	// Read facilities
	facilities = new string[numFacilities];
	for( int i = 0; i < numFacilities; i++ ) {
		getline( inn, facilities[i] );
	}

	// Read rooms
	for (unsigned roomIdx = 0; roomIdx < 3; roomIdx++) {
		rooms[roomIdx] = new List(Sorted);
		
		int numRooms = 0;
		inn >> numRooms;
		inn.ignore();

		for (unsigned j = 0; j < numRooms; j++) {
			int roomNum = 0;
			inn >> roomNum;
			inn.ignore();

			Room *room = NULL;
			switch (roomIdx) {
				case 0: room = new RoomSingle(inn, roomNum); break;
				case 1: room = new RoomDouble(inn, roomNum); break;
				case 2: room = new RoomSuite(inn,  roomNum); break;
			}

			rooms[roomIdx]->add(room);
		}
	}
}

Hotel::~Hotel(void) {
	delete rooms[0];
	delete rooms[1];
	delete rooms[2];

	if (facilities) {
		delete[] facilities;
	}
}

string Hotel::getFileName() {
	return fileName;
}


/*
==================
Basic command methods
==================
*/
void Hotel::bookRoom() {
	Reservation *res = NULL;
	
	int rType = ReadIntWithOptions("What kind of room would you like to book?",
								   3, "Single room", "Double room", "Suite");
	rType -= 1;
	
	/* Read arrival date */
	int arrDate = ReadDate("Date of arrival", Singletons::currentDate);
	res = new Reservation(arrDate);
	
	/* Read departure date */
	Date depDate = ReadDate(
		"Date of departure",
		Singletons::timer->nestedato(arrDate)
	);
	
	/* Read breakfast requirement */
	int reqBreakfast = ReadIntWithOptions("Included breakfast?",2,"No","Yes");
	reqBreakfast -= 1;
	
	/* Read the number of required beds */
	int bedReq = ReadInt("How many beds are needed?", 1);
	
	/* Find a suitable room */
	Room *room = getRoom(
		arrDate,
		depDate,
		reqBreakfast,
		rType,
		bedReq
	);
	
	if (!room) {
		printf("\tNo available rooms found.\n");
		delete res;
		return;
	} else {
		res->room = room;
		res->setDepartureDate(depDate);
		printf("\n\tRoom %i selected.\n\n", *room->roomNumber);
	}

	/* Set the BedStatus in the Reservation */
	if (rType == 1) {
		if (((RoomDouble*)room)->extraBedAvailable) {
			if (room->getBedCount() == bedReq) {
				res->bedStatus = Reservation::BED_TAKEN;
				res->addBill("Extra bed", extraBedPrice);
			} else {
				res->bedStatus = Reservation::BED_AVAILABLE;
			}
		} else {
			res->bedStatus = Reservation::BED_UNAVAILABLE;
		}
	} else {
		/* Singlerooms and suites does not have an extra bed */
		res->bedStatus = Reservation::BED_UNAVAILABLE;
	}

	/* Read residents */
	res->readResidents();
	
	/* Add the reservation to the room */
	room->reservations->add(res);
}

void Hotel::cancelRoom() {
	/* Retreive the reservations of the customer */
	string name = ReadString("Customer's name");
	List *reservs = getReservations(name);

	if (!reservs->no_of_elements()) {
		printf("\tNo reservations has been made in that name.\n");
		return;
	}

	/* Count "cancellable" reservations, print warning message if
	 * no made reservations can be cancelled. 
	 */
	int numPossible = 0;
	
	/* Display and query for deletion */
	for (unsigned i = 0; i < reservs->no_of_elements(); i++) {
		Reservation *res = (Reservation*)reservs->remove_no(i+1);
		
		if (!res->isCheckedIn()) {
			numPossible++;

			res->display();
			
			int sel = ReadIntWithOptions("Cancel reservation?", 2, "Yes", "no");
			if (sel == 1) {		// Yes
				/* Delete the reservation, decrement "i" */
				res->room->removeReservation(res);
				i--;
			} else {			// No
				/* Add the reservation back to the list */
				reservs->add(res);
			}
		}
	}

	/* Print warning message if no cancellations were possible */
	if (!numPossible) {
		printf("\tNo reservations that can be cancelled.\n");
	}
	
	/* Delete the list */
	clearListSorted(reservs);
	delete reservs;
}

void Hotel::checkIn() {
	string customer = ReadString("Customer's name");
	List *reservs = getReservations(customer);
	
	/* Remove reservations where 'arrivalDate' != 'currentDate' */
	Date currentDate = Singletons::currentDate;
	for (unsigned i = 0; i < reservs->no_of_elements(); i++) {
		Reservation *res = (Reservation*)reservs->remove_no(i+1);
		
		if (res->arrivalDate == currentDate && !res->isCheckedIn()) {
			reservs->add(res);
		} else {
			i--;
		}
	}
	
	int resCount = reservs->no_of_elements();

	/* Return if no reservations apply */
	if (resCount == 0) {
		printf("\tNo reservations to check in.\n");
		return;
	}

	/* Display the number of relevant reservations */
	printf("\tThere are %i reservation%s in that name.\n\n",
		resCount, (resCount==1?"":"s")
	); 

	
	/* Read additional residents, flag 'checkedIn' */
	for (unsigned i = 0; i < reservs->no_of_elements(); i++) {
		printf("\nReservation %i of %i:\n", i+1, resCount);
		Reservation *res = (Reservation*)reservs->remove_no(i+1);
		reservs->add(res);
		
		res->checkedIn = true;
		res->readAdditionalResidents();
		printf("\t%s checked in.\n", customer.c_str());
	}
}

void Hotel::checkOut() {
	int roomNum = ReadInt("Room number", 101);
	Room *room = getRoom(roomNum);
	
	if (!room) {
		printf("\tRoom does not exist.\n");
		return;
	}
	
	/* Get the reservation, ensure it expires today */
	Reservation *res = room->getCurrentReservation();
	if (!res) {
		printf("\tNo active reservation in that room.\n");
		return;
	} else if (res->departureDate != Singletons::currentDate) {
		printf("\tThat customer doesn't leave until %s.\n",
			   dateToString(Singletons::currentDate).c_str()
		);
		return;
	}
	
	/* Print and sum all the expenses */
	float totalSum = 0.f;
	
	printf("\tExpenses:\n");
	
	// Print the daily prices
	printf("\t\tPrice per night:\n");
	Date date = Singletons::currentDate;
	for (unsigned i = 0; i < res->lengthOfStay; i++) {
		totalSum += res->dailyPrices[i];
		
		printf("\t\t\t%s:  %0.1f\n",
				dateToString(date).c_str(),
			   	res->dailyPrices[i]
		);
		
		date = Singletons::timer->nestedato(date);
	}
	
	// Print the cost of breakfast
	if (!room->breakfastIncluded) {
		totalSum += breakfastPrice * res->lengthOfStay;
		printf("\t\tBreakfast:  %i\n", breakfastPrice * res->lengthOfStay);
	}
	
	// Print the const of an extra bed
	if (res->bedStatus == Reservation::BED_TAKEN) {
		totalSum += extraBedPrice * res->lengthOfStay;
		printf("\t\tExtra bed:  %i\n", extraBedPrice * res->lengthOfStay);
	}
	
	// Print any additional bills
	if (res->bills->no_of_elements()) {
		printf("\t\tUsage of facilities:\n");
		
		for (unsigned i = 0; i < res->bills->no_of_elements(); i++) {
			Bill *bill = (Bill*)res->bills->remove();
			res->bills->add(bill);
			
			totalSum += bill->GetSum();
			
			printf("\t\t\t%s:  %0.1f\n",
				   bill->GetDescription().c_str(),
				   bill->GetSum()
			);
		}
	}
	
	printf("\n\tTotal sum: %0.2f\n", totalSum);
	
	/* Append it to [HOTEL].HST */
	ofstream file((fileName + ".HST").c_str(), ios::app);
	res->WriteToFile(file);
	
	/* Remove it from the room */
	for (unsigned i = 0; i < room->reservations->no_of_elements(); i++) {
		Reservation *r = (Reservation*)room->reservations->remove_no(i+1);
		
		if (r != res) {
			room->reservations->add(r);
		}
	}
	
	/* Delete it. Delete it good. */
	delete res;
}

void Hotel::addExpenses() {
	int roomNum = ReadInt("Room number", 101);
	Room *room = getRoom(roomNum);
	
	if (room) {
		Reservation *res = room->getCurrentReservation();
		if (res && res->isCheckedIn()) {
			Bill *bill = new Bill;
			res->bills->add(bill);
		} else {
			printf("\tNo current resident in room.");
		}
	} else {
		printf("\tThat room does not exist.\n");
	}
}

void Hotel::WriteToFile() {
	printf("\tWriting hotel data to \"%s\"...  ", (fileName+".dta").c_str());
	
	ofstream file((fileName + ".dta").c_str());
	if (!file.is_open()) {
		string errmsg = "Failed to open hotel file '"
		+ fileName + "' for writing.";
		Critical(errmsg);
	}
	
	file << name;
	file << endl;
	
	file << address;
	file << endl;
	
	file << postalCode;
	file << endl;
	
	file << mailAddress;
	file << endl;
	
	file << phone;
	file << endl;
	
	file << fax;
	file << endl;
	
	file << breakfastPrice;
	file << endl;
	
	file << extraBedPrice;
	file << endl;
	
	file << numFacilities;
	file << endl;
	
	for (unsigned i = 0; i < numFacilities; i++) {
		file << facilities[i];
		file << endl;
	}
	
	for (unsigned lst = 0; lst < 3; lst++) {
		file << rooms[lst]->no_of_elements();
		file << endl;
		
		for (unsigned i = 0; i < rooms[lst]->no_of_elements(); i++) {
			Room *room = (Room*)rooms[lst]->remove_no(i+1);
			rooms[lst]->add(room);
			
			room->WriteToFile(file);
		}
	}
	
	printf("Done!\n");
}


/*
==================
Edit methods
==================
*/
void Hotel::editDatesBeforeCheckIn() {
	string customer = ReadString("Customer name");
	List *reservations = getReservations(customer);
	
	if (reservations->no_of_elements()) {
		for (unsigned i = 0; i < reservations->no_of_elements(); i++) {
			Reservation *res = (Reservation*)reservations->remove_no(i+1);
			reservations->add(res);
			
			if (!res->isCheckedIn()) {
				res->display();

				int sel = ReadIntWithOptions(
					"Which date will you change?", 3,
					"Arrival", "Departure", "None"
				);

				/* Prepare the min-max values */
				Date min, max;

				if (sel == 1) {				// Arrival
					min = Singletons::currentDate;
					max = res->departureDate;
				} else if (sel == 2) {		// Departure
					min = Singletons::timer->nestedato(res->arrivalDate);
					max = INT_MAX;
				} else {					// None
					 return;
				}

				// Read the new date
				Date newDate = ReadDate("New date", min, max);

				// Set the new date
				if (sel == 1) {
					res->setArrivalDate(newDate);
				} else {
					res->setDepartureDate(newDate);
				}
			}
			else {
				printf("+tThis customer is already cheaked in");
			}
		}
	} else {
		printf("\tNo reservations has been made in that name.\n");
	}
}

void Hotel::editDepartureDateAfterCheckIn() {
	int roomNum = ReadInt("Room number", 101);
	Room *room = getRoom(roomNum);

	if (!room) {
		printf("\tThat room does not exist.\n");
		return;
	}

	Reservation *res = room->getCurrentReservation();
	if (res && res->isCheckedIn()) {
		res->display();

		char c = ReadChar("Change departure date? (Y/N)");
		if (toupper(c) == 'Y') {
			// Get the highest alternative of TODAY and the day after the 
			// arrival date of the reservation.
			Date min = MAX(	Singletons::currentDate, 
							Singletons::timer->nestedato(res->arrivalDate)
			);

			Date date = ReadDate("New date of departure", min);

			res->setDepartureDate(date);
		}
	} else {
		printf("\tNo current resident in room.\n");
	}
}

void Hotel::changeRoom() {
	/* ASSUMPTIONS:
	 * - The new room must be of the same type.
	 *		(TO DO: This will be changed in the future)
	 * - The new room must have identical properties (inc. breakfast, extra bed).
	 * - It does not matter if the resident has checked in or not.
	 */

	int roomNum = ReadInt("Room number", 101);
	Room *room = getRoom(roomNum);
	if (!room) {
		printf("\tThat room does not exist.\n");
	}
	Reservation *res = room->getCurrentReservation();
	if (res) {
		List *similarRooms = new List(Sorted);

		/* Find similar rooms */
		unsigned type = room->GetType();

		for (unsigned i = 0; i < rooms[type]->no_of_elements(); i++) {
			Room *r = (Room*)rooms[type]->remove_no(i+1);
			rooms[type]->add(r);
			
			if (r == room) {
				continue;
			}

			if (r->breakfastIncluded != room->breakfastIncluded) {
				continue;
			}

			if (r->getBedCount() != room->getBedCount()) {
				continue;
			}

			/* Make sure the room is available */
			Date start = Singletons::currentDate;
			Date end   = res->departureDate;
			if (!r->AvailableInPeriod(start, end)) {
				continue;
			}

			/* The room passed all the checks */
			similarRooms->add(r);
		}

		/* Return if no rooms were found */
		if (similarRooms->no_of_elements() == 0) {
			printf("\tNo similar rooms found.\n");
			return;
		}

		/* Store the room numbers in a vector */
		vector<string> roomNumbers;
		for (unsigned i = 0; i < similarRooms->no_of_elements(); i++) {
			Room *r = (Room*)similarRooms->remove_no(i+1);
			similarRooms->add(r);

			stringstream ss;
			ss << r->getRoomNumber();
			roomNumbers.push_back(ss.str());
		}

		roomNumbers.push_back("None (cancel)");

		/* Read input from user */
		int selection = ReadIntWithOptions("Available rooms of same kind", 
											roomNumbers);

		/* The user cancelled */
		if (selection == roomNumbers.size()) {
			printf("\tThe resident was not moved.\n");
			return;
		}

		Room *newRoom = (Room*)similarRooms->remove_no(selection);

		/* Remove the reservation from the current room */
		room->removeReservation(res);

		/* Add the reservation to the new room */
		newRoom->addReservation(res);

		printf("\tMoved resident to room %s.\n", 
					roomNumbers[selection-1].c_str());

		/* Clear the list */
		clearListSorted(similarRooms);
		delete similarRooms;
	} else {
		printf("\tNo current resident.\n");
	}
}

/*
==================
Overview methods
==================
*/
void Hotel::displayInfo() {
	printf("\n\tGeneral:\n");
	printf("\t\t%s\n", name.c_str());
	printf("\t\t%s\n", address.c_str());
	printf("\t\t%s\n", postalCode.c_str());
	
	printf("\tContact:\n");
	printf("\t\tPhone:     %i\n", phone);
	printf("\t\tFax:       %i\n", fax);
	printf("\t\tE-mail:    %s\n", mailAddress.c_str());
	
	printf("\tPricing:\n");
	printf("\t\tBreakfast: %i\n", breakfastPrice);
	printf("\t\tExtra bed: %i\n", extraBedPrice);
	
	if (numFacilities) {
		printf("\tFacilities:\n");
		
		for (unsigned i=0; i<numFacilities; i++) {
			printf("\t\t%s\n", facilities[i].c_str());
		}
	}
}

void Hotel::displaySuites() {
	/* Iterate over all the suites */
	for (unsigned i = 0; i < rooms[2]->no_of_elements(); i++) {
		RoomSuite *suite = (RoomSuite*)rooms[2]->remove_no(i+1);
		rooms[2]->add(suite);
		
		suite->display();
	}
}

void Hotel::displayReservationsByPerson() {
	/* Retrieve the list of reservations */
	string name = ReadString("The name of the customer");
	List *reservs = Singletons::hotel->getReservations(name);
	
	if (!reservs->no_of_elements()) {
		printf("\n\tNo reservations has been made for %s.\n", name.c_str());
	} else {
		reservs->display_list();
		clearListSorted(reservs);
	}
	
	/* Delete the temporary list */
	delete reservs;
}

void Hotel::whenIsRoomAvailable() {
	Room *room = getRoom();
	
	if (!room) {
		printf("\tRoom does not exist.\n");
	} else {
		Date cur = Singletons::currentDate;		// 'Iteration'-date
		List *reservations = room->reservations;
		 
		for (unsigned i = 0; i < reservations->no_of_elements(); i++) {
			Reservation *res = (Reservation*)reservations->remove_no(i+1);
			reservations->add(res);
			
			if (cur >= res->arrivalDate && cur <= res->departureDate) {
				cur = Singletons::timer->nestedato(res->departureDate);
			} else if (cur < res->arrivalDate) {
				printf("\t%s - %s\n",
					   dateToString(cur).c_str(),
					   dateToString(res->arrivalDate).c_str()
				);
				
				cur = Singletons::timer->nestedato(res->departureDate);
			}
		}
		
		printf("\t%s - ...\n", dateToString(cur).c_str());
	}
}

void Hotel::displayReservationsForRoom() {
	Room *room = getRoom();
	room->reservations->display_list();
}

void Hotel::displayResidentInRoom() {
	Room *room = getRoom();
	if (!room) {
		printf("\tThat room does not exist.\n");
		return;
	}

	Reservation *reservation = room->getCurrentReservation();
	if (!reservation) {
		printf("\tNo current resident in that room.\n");
		return;
	}
	
	/* Print the names of the customer(s) */
	string* name = reservation->getCustomerName();
	int numOfCustomers = reservation->numOfCustomers();

	if( numOfCustomers == 1) {
		printf("\n\tResident name:\n\t\t%s\n", name->c_str());
	}
	else {
		for(unsigned i = 0; i < numOfCustomers; i++) {
			printf("\n\tResident %i's name:\n\t\t%s", 
				i+1, name[i].c_str());
		}
		printf("\n");
	}

	/* Print the arrival and departure dates */
	printf("\n\tArrival date:   ");
	printDate(reservation->arrivalDate);

	printf("\n\tDeparture date: ");
	printDate(reservation->departureDate);

	printf("\n\tLength of stay: %i days\n",
		Singletons::timer->forskjell_datoer(
			reservation->arrivalDate, 
			reservation->departureDate
		)
	);
	
	room->display();
	reservation->printBills();
}

void Hotel::displayAvailableRoomsInPeriod() {
	Date current = Singletons::currentDate;
	
	int type = ReadIntWithOptions("Type of room", 3, "Single", "Double", "Suite");
	type -= 1;
	
	Date start = ReadDate("Start of period", current);
	Date end = ReadDate("End of period", Singletons::timer->nestedato(start));
	
	printf("\n\tRooms available in the period %s - %s:\n",
		   dateToString(start).c_str(), dateToString(end).c_str()
	);
	
	for (unsigned j = 0; j < rooms[type]->no_of_elements(); j++) {
		Room *room = (Room*)rooms[type]->remove_no(j+1);
		rooms[type]->add(room);
			
		if (room->AvailableInPeriod(start, end)) {
			printf("\t\t%i\n", room->number);
		}
	}
}

/*
==================
Getter methods
==================
*/
float Hotel::getBreakfastPrice() {
	return breakfastPrice;
}

/*
==================
Private methods
==================
*/
List* Hotel::getReservations(string customerName) {
	customerName = capitalize(customerName);
	List *reservations = new List(Sorted);
	
	for (unsigned i = 0; i < 3; i++) {

		/* Iterate over all the rooms */
		for (unsigned j = 0; j < rooms[i]->no_of_elements(); j++) {
			Room *room = (Room*)rooms[i]->remove_no(j+1);
			rooms[i]->add(room);
			
			/* Iterate over all the reservations */
			for (unsigned k = 0; k < room->reservations->no_of_elements(); k++) {
				Reservation *res = (Reservation*)room->reservations->remove_no(k+1);
				room->reservations->add(res);
				
				if (capitalize(res->residentNames[0]) == customerName) {
					reservations->add(res);
				}
			}
		}
	}
	
	return reservations;
}

Room* Hotel::getRoom() {
	int roomNum = ReadInt("Room number", 101);
	return getRoom(roomNum);
}

Room* Hotel::getRoom(int roomNum) {
	for (unsigned i = 0; i < 3; i++) {
		for (unsigned j = 0; j < rooms[i]->no_of_elements(); j++) {
			Room *r = (Room*)rooms[i]->remove_no(j+1);
			rooms[i]->add(r);

			if (r->getRoomNumber() == roomNum) {
				return r;
			}
		}
	}
	
	return NULL;
}

Room* Hotel::getRoom(Date d1, Date d2, bool breakfast, 
					 int type, int bedReq) {
	if (type < 0 || type > 2) {
		Error("Hotel::getRoom, bad type param", type);
		return NULL;
	}

	if (rooms[type]->no_of_elements() == 0) {
		return NULL;
	}

	/* To check for a room in a random order, "index" is initiated
	 * to a value between 0 and n. N is reset to 0 when "index >= n"
	 * in the loop to ensure that all the rooms are being iterated over
	 * until a suitable room has been found.
	 */
	int ran = rand();
	if (ran < 0) {
		ran = 1;
	}

	/* Unless a room with exectly "bedReq" is found,
	 * a room with _more_ beds is chosen. 
	 */
	Room *secChoice = NULL; 	// Second choice
	int bestOffset = 0;			// The difference in bed-count

	unsigned index = ran % rooms[type]->no_of_elements();

	for (unsigned i=0; i<rooms[type]->no_of_elements(); i++, index++) {
		if (index >= rooms[type]->no_of_elements()) {
			index = 0;
		}

		Room *room = (Room*)rooms[type]->remove_no(index+1);
		rooms[type]->add(room);

		if (room->AvailableInPeriod(d1, d2)
		&& room->breakfastIncluded == breakfast) {
			int offset = bedReq - room->getBedCount();

			if (offset == 0) {
				/* Perfect fit */
				return room;
			} else if (offset < bestOffset) {
				/* We found an inefficient room */
				secChoice = room;
				bestOffset = offset;
			}
		}
	}
	
	return secChoice;
}
